/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package bgu.sonar.util.collections;

import com.google.common.base.Predicate;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;

/**
 *
 * @author bennyl
 */
public class LispImmutableList<A> extends LispPair<A, LispImmutableList> implements Iterable<A> {

    public static LispImmutableList NIL = new LispImmutableList(null, null);

    private LispImmutableList(A car, LispImmutableList cdr) {
        super(car, cdr);
    }

    /**
     * return a new list with A added to the head of it
     *
     * @param added
     * @return
     */
    public LispImmutableList<A> with(A added) {
        return new LispImmutableList(added, this);
    }

    /**
     * linear seek for the given value (calling equals) return a new list that does not contain the first occurrence of the given value
     *
     * @param removed
     * @return
     */
    public LispImmutableList<A> without(final A toRemove) {
        return cons(filteredIterator(new Predicate<A>() {
            boolean found = false;

            @Override
            public boolean apply(A t) {
                if (found) {
                    return true;
                }
                if (toRemove.equals(t)) {
                    found = true;
                    return false;
                }

                return true;
            }
        }));
    }

    public LinkedList<A> linkify() {
        LinkedList<A> result = new LinkedList<>();
        LispImmutableList<A> c = this;
        while (c != NIL) {
            result.add(c.car());
            c = c.cdr();
        }

        return result;
    }

    public static <A> LispImmutableList<A> cons(A... collection) {
        LispImmutableList result = NIL;
        for (int i = collection.length - 1; i >= 0; i--) {
            result = new LispImmutableList(collection[i], result);
        }

        return result;
    }

    public static <A> LispImmutableList<A> cons(Iterator<A> items) {
        LispImmutableList result = NIL;
        while (items.hasNext()) {
            result = new LispImmutableList(items.next(), result);
        }

        return result.reverse();
    }

    public boolean isEmpty() {
        return this == NIL;
    }

    public static <A> LispImmutableList<A> cons(Collection<A> collection) {
        return (LispImmutableList<A>) cons(collection.toArray());
    }

    public Iterator<A> filteredIterator(final Predicate<A> predicate) {
        return new Iterator<A>() {
            LispImmutableList<A> c = filter(LispImmutableList.this);

            @Override
            public boolean hasNext() {
                return c != NIL;
            }

            private LispImmutableList<A> filter(LispImmutableList<A> c) {
                while (c != NIL) {
                    if (predicate.apply(c.car())) {
                        return c;
                    }

                    c = c.cdr();
                }

                return c;
            }

            @Override
            public A next() {
                A result = c.car();
                c = filter(c);
                return result;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("Not supported.");
            }
        };
    }

    @Override
    public Iterator<A> iterator() {
        return new Iterator<A>() {
            LispImmutableList<A> next = LispImmutableList.this;

            @Override
            public boolean hasNext() {
                return next != NIL;
            }

            @Override
            public A next() {
                A result = next.car();
                next = next.cdr();

                return result;
            }

            @Override
            public void remove() {
                throw new UnsupportedOperationException("Not supported.");
            }
        };
    }

    public <A> LispImmutableList<A> reverse() {
        LispImmutableList result = NIL;
        LispImmutableList c = this;
        while (c != NIL) {
            result = new LispImmutableList(c.car(), result);
        }

        return result;
    }
}
